<!doctype html>
<!--
@license
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="utf-8">
  <script src="../../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
  <script src="wct-browser-config.js"></script>
  <script src="../../node_modules/wct-browser-legacy/browser.js"></script>
  <script type="module">
    import { setRemoveNestedTemplates, setFastDomIf } from '../../lib/utils/settings.js';
    setRemoveNestedTemplates(location.search.match(/removeNestedTemplates/));
    setFastDomIf(location.search.match(/fastDomIf/));
  </script>
  <script type="module" src="../../polymer-legacy.js"></script>
  <script type="module" src="./dom-if-elements.js"></script>
</head>
<body>

  <x-nested-if-configured id="configured"></x-nested-if-configured>

  <x-nested-if-individual id="individual"></x-nested-if-individual>

  <dom-bind id="unconfigured">
    <template>
      <x-nested-if id="unconfigured1"></x-nested-if>
      <x-nested-if id="unconfigured2"></x-nested-if>
    </template>
  </dom-bind>

  <div id="inDocumentContainer">
    <dom-if id="inDocumentIf">
      <template>
        <x-foo
               prop="{{prop}}"
               item-prop="{{item.prop}}">
        </x-foo>
        <template is="dom-if">
          <x-foo
                 prop="{{prop}}"
                 item-prop="{{item.prop}}">
          </x-foo>
          <template is="dom-if">
            <x-foo
                   prop="{{prop}}"
                   item-prop="{{item.prop}}">
            </x-foo>
          </template>
        </template>
      </template>
    </dom-if>
  </div>

  <div id="structuredContainer">
    <dom-bind id="structuredDomBind">
      <template>
        <template is="dom-if" id="structuredDomIf" if="{{item.show}}">
          <div class="showing"></div>
        </template>
      </template>
    </dom-bind>
  </div>

  <div id="outerContainer">
    <dom-if id="simple">
      <template>
        <x-client></x-client>
        <x-client></x-client>
        <x-client></x-client>
      </template>
    </dom-if>

    <div id="innerContainer">
    </div>
  </div>

  <div id="removalContainer">
    <dom-if if id="toBeRemoved">
      <template><div id="shouldBeRemoved"></div></template>
    </dom-if>
  </div>

  <script type="module">
import './dom-if-elements.js';
</script>

  <div id="nonUpgrade">
    <dom-if if>
      <template>stamped</template>
    </dom-if>
  </div>


  <script type="module">
import './dom-if-elements.js';
import { flush } from '../../lib/utils/flush.js';
/* global configured individual unconfigured1 unconfigured2 inDocumentContainer inDocumentIf structuredContainer structuredDomIf structuredDomBind outerContainer innerContainer shouldBeRemoved toBeRemoved removalContainer nonUpgrade*/
suite('nested pre-configured dom-if', function() {

  test('parent scope binding', function() {
    let stamped = configured.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');
    assert.equal(stamped[0].prop, 'outer');
    assert.equal(stamped[0].itemProp, 'outerItem');
    assert.equal(stamped[1].prop, 'outer');
    assert.equal(stamped[1].itemProp, 'outerItem');
    assert.equal(stamped[2].prop, 'outer');
    assert.equal(stamped[2].itemProp, 'outerItem');
  });

  test('parent scope downward notification', function() {
    let stamped = configured.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    configured.prop = 'yes';
    assert.equal(stamped[0].prop, 'yes');
    assert.equal(stamped[1].prop, 'yes');
    assert.equal(stamped[2].prop, 'yes');
    configured.set('item.prop', 'yay');
    assert.equal(stamped[0].itemProp, 'yay');
    assert.equal(stamped[1].itemProp, 'yay');
    assert.equal(stamped[2].itemProp, 'yay');
  });

  test('parent upward upward notification', function() {
    let stamped = configured.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    stamped[2].prop = 'nice';
    assert.equal(configured.prop, 'nice');
    assert.equal(stamped[0].prop, 'nice');
    assert.equal(stamped[1].prop, 'nice');
    assert.equal(stamped[2].prop, 'nice');
  });

  test('dom-change event composed, bubbles outside dom-if scope', function() {
    let domChangeFired = 0;
    let domIf = configured.$['if-1'];
    configured.addEventListener('dom-change', function() {
      domChangeFired++;
    });
    domIf.if = !domIf.if;
    domIf.render();
    domIf.if = !domIf.if;
    domIf.render();
    assert.equal(domChangeFired, 2);
  });

});

suite('nested individually-controlled dom-if', function() {

  test('nothing stamped', function() {
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 0, 'total stamped count incorrect');
  });

  test('show 1', function() {
    individual.shouldStamp1 = true;
    individual.render();
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 1, 'total stamped count incorrect');
    assert.equal(stamped[0].prop, 'prop1');
    assert.equal(getComputedStyle(stamped[0]).display, 'inline', 'stamped 1 display wrong');
  });

  test('show 2', function() {
    individual.shouldStamp2 = true;
    individual.render();
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 2, 'total stamped count incorrect');
    assert.equal(stamped[0].prop, 'prop1');
    assert.equal(stamped[1].prop, 'prop2');
    assert.equal(getComputedStyle(stamped[0]).display, 'inline', 'stamped 1 display wrong');
    assert.equal(getComputedStyle(stamped[1]).display, 'inline', 'stamped 2 display wrong');
  });

  test('show 3-1', function() {
    individual.shouldStamp3_1 = true;
    individual.render();
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');
    assert.equal(stamped[0].prop, 'prop1');
    assert.equal(stamped[1].prop, 'prop2');
    assert.equal(stamped[2].prop, 'prop3_1');
    assert.equal(getComputedStyle(stamped[0]).display, 'inline', 'stamped 1 display wrong');
    assert.equal(getComputedStyle(stamped[1]).display, 'inline', 'stamped 2 display wrong');
    assert.equal(getComputedStyle(stamped[2]).display, 'inline', 'stamped 3-1 display wrong');
  });

  test('show 3-2', function() {
    individual.shouldStamp3_2 = true;
    individual.render();
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 4, 'total stamped count incorrect');
    assert.equal(stamped[0].prop, 'prop1');
    assert.equal(stamped[1].prop, 'prop2');
    assert.equal(stamped[2].prop, 'prop3_1');
    assert.equal(stamped[3].prop, 'prop3_2');
    assert.equal(getComputedStyle(stamped[0]).display, 'inline', 'stamped 1 display wrong');
    assert.equal(getComputedStyle(stamped[1]).display, 'inline', 'stamped 2 display wrong');
    assert.equal(getComputedStyle(stamped[2]).display, 'inline', 'stamped 3-1 display wrong');
    assert.equal(getComputedStyle(stamped[3]).display, 'inline', 'stamped 3-2 display wrong');
  });

  test('remove 3-2', function() {
    individual.shouldStamp3_2 = false;
    individual.render();
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');
    assert.equal(stamped[0].prop, 'prop1');
    assert.equal(stamped[1].prop, 'prop2');
    assert.equal(stamped[2].prop, 'prop3_1');
    assert.equal(getComputedStyle(stamped[0]).display, 'inline', 'stamped 1 display wrong');
    assert.equal(getComputedStyle(stamped[1]).display, 'inline', 'stamped 2 display wrong');
    assert.equal(getComputedStyle(stamped[2]).display, 'inline', 'stamped 3-1 display wrong');
  });

  test('show 3-3', function() {
    individual.shouldStamp3_3 = true;
    individual.render();
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 4, 'total stamped count incorrect');
    assert.equal(stamped[0].prop, 'prop1');
    assert.equal(stamped[1].prop, 'prop2');
    assert.equal(stamped[2].prop, 'prop3_1');
    assert.equal(stamped[3].prop, 'prop3_3');
    assert.equal(getComputedStyle(stamped[0]).display, 'inline', 'stamped 1 display wrong');
    assert.equal(getComputedStyle(stamped[1]).display, 'inline', 'stamped 2 display wrong');
    assert.equal(getComputedStyle(stamped[2]).display, 'inline', 'stamped 3-1 display wrong');
    assert.equal(getComputedStyle(stamped[3]).display, 'inline', 'stamped 3-3 display wrong');
  });

  test('update 3-3', function() {
    individual.prop3_3 = 'prop3_3*';
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 4, 'total stamped count incorrect');
    assert.equal(stamped[0].prop, 'prop1');
    assert.equal(stamped[1].prop, 'prop2');
    assert.equal(stamped[2].prop, 'prop3_1');
    assert.equal(stamped[3].prop, 'prop3_3*');
  });

  test('remove 3-3', function() {
    individual.shouldStamp3_3 = false;
    individual.render();
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');
    assert.equal(stamped[0].prop, 'prop1');
    assert.equal(stamped[1].prop, 'prop2');
    assert.equal(stamped[2].prop, 'prop3_1');
    assert.equal(getComputedStyle(stamped[0]).display, 'inline', 'stamped 1 display wrong');
    assert.equal(getComputedStyle(stamped[1]).display, 'inline', 'stamped 2 display wrong');
    assert.equal(getComputedStyle(stamped[2]).display, 'inline', 'stamped 3-1 display wrong');
  });

  test('remove 3', function() {
    individual.shouldStamp3_1 = false;
    individual.render();
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 2, 'total stamped count incorrect');
    assert.equal(stamped[0].prop, 'prop1');
    assert.equal(stamped[1].prop, 'prop2');
    assert.equal(getComputedStyle(stamped[0]).display, 'inline', 'stamped 1 display wrong');
    assert.equal(getComputedStyle(stamped[1]).display, 'inline', 'stamped 2 display wrong');
  });

  test('hide 2', function() {
    individual.shouldStamp2 = false;
    individual.render();
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 2, 'total stamped count incorrect');
    assert.equal(getComputedStyle(stamped[0]).display, 'inline', 'stamped 1 display wrong');
    assert.equal(getComputedStyle(stamped[1]).display, 'none', 'stamped 2 display wrong');
  });

  test('hide 1', function() {
    individual.shouldStamp1 = false;
    individual.render();
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 2, 'total stamped count incorrect');
    assert.equal(getComputedStyle(stamped[0]).display, 'none', 'stamped 1 display wrong');
    assert.equal(getComputedStyle(stamped[1]).display, 'none', 'stamped 2 display wrong');
  });

  test('show 1', function() {
    individual.shouldStamp1 = true;
    individual.render();
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 2, 'total stamped count incorrect');
    assert.equal(getComputedStyle(stamped[0]).display, 'inline', 'stamped 1 display wrong');
    assert.equal(getComputedStyle(stamped[1]).display, 'none', 'stamped 2 display wrong');
  });

  test('show 2', function() {
    individual.shouldStamp2 = true;
    individual.render();
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 2, 'total stamped count incorrect');
    assert.equal(getComputedStyle(stamped[0]).display, 'inline', 'stamped 1 display wrong');
    assert.equal(getComputedStyle(stamped[1]).display, 'inline', 'stamped 2 display wrong');
  });

  test('show 3', function() {
    individual.shouldStamp3_1 = true;
    individual.render();
    let stamped = individual.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');
    assert.equal(getComputedStyle(stamped[0]).display, 'inline', 'stamped 1 display wrong');
    assert.equal(getComputedStyle(stamped[1]).display, 'inline', 'stamped 2 display wrong');
    assert.equal(getComputedStyle(stamped[2]).display, 'inline', 'stamped 3 display wrong');
  });

});

suite('nested un-configured dom-if in document', function() {

  test('if=false: nothing rendered', function() {
    let stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if)');
    assert.equal(stamped.length, 0, 'total stamped count incorrect');
  });

  test('if=true: everything rendered and visible', function() {
    // first dom-if
    inDocumentIf.if = true;
    inDocumentIf.render();
    let stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 1, 'total stamped count incorrect');

    // second dom-if
    let xif = inDocumentContainer.querySelector('dom-if');
    xif.if = true;
    xif.render();
    stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 2, 'total stamped count incorrect');

    // third dom-if
    xif = inDocumentContainer.querySelector('dom-if');
    xif.if = true;
    xif.render();
    stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');

    stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if)');
    stamped.forEach(function(n) {
      assert.equal(getComputedStyle(n).display, 'inline', 'node was hidden but should not have been');
    });
  });

  test('if=false, restamp=false: everything hidden', function() {
    inDocumentIf.if = false;
    inDocumentIf.render();
    let stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');
    stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if)');
    stamped.forEach(function(n) {
      assert.equal(getComputedStyle(n).display, 'none', 'node was not hidden but should have been');
    });
  });

  test('if=true, restamp=true, everything rendered and visible', function() {
    inDocumentIf.restamp = true;
    inDocumentIf.if = true;
    inDocumentIf.render();
    let stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');
    stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if)');
    stamped.forEach(function(n) {
      assert.equal(getComputedStyle(n).display, 'inline', 'node was hidden but should not have been');
    });
  });

  test('if=false, restamp=true, everything gone', function() {
    inDocumentIf.restamp = true;
    inDocumentIf.if = false;
    inDocumentIf.render();
    // 2nd one needed to force nested if to detach
    let stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if)');
    assert.equal(stamped.length, 0, 'total stamped count incorrect');
  });

  // repeat, just to get everything rendered again...
  test('if=true: everything rendered and visible', function() {
    // first dom-if
    inDocumentIf.if = true;
    inDocumentIf.render();
    let stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 1, 'total stamped count incorrect');

    // second dom-if
    let xif = inDocumentContainer.querySelector('dom-if');
    xif.if = true;
    xif.render();
    stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 2, 'total stamped count incorrect');

    // third dom-if
    xif = inDocumentContainer.querySelector('dom-if');
    xif.if = true;
    xif.render();
    stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');

    stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if)');
    stamped.forEach(function(n) {
      assert.equal(getComputedStyle(n).display, 'inline', 'node was hidden but should not have been');
    });
  });

  test('parent scope binding', function() {
    let stamped = inDocumentContainer.querySelectorAll('*:not(template):not(dom-if):not(span)');
    stamped[0].prop = 'outer';
    assert.equal(stamped[1].prop, 'outer');
    assert.equal(stamped[2].prop, 'outer');
  });

});

suite('nested un-configured dom-if', function() {

  test('if=false: nothing rendered', function() {
    let stamped = unconfigured1.shadowRoot.querySelectorAll('*:not(template):not(dom-if)');
    assert.equal(stamped.length, 0, 'total stamped count incorrect');
  });

  test('if=true: everything rendered and visible', function() {
    unconfigured1.domUpdateHandlerCount = 0;
    unconfigured1.shouldStamp = true;
    unconfigured2.shouldStamp = true;
    unconfigured1.render();
    let stamped = unconfigured1.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');
    stamped[0].prop = 'outer';
    assert.equal(unconfigured1.domUpdateHandlerCount, 1);
  });

  test('if=false, restamp=false: everything hidden', function() {
    unconfigured1.domUpdateHandlerCount = 0;
    unconfigured1.shouldStamp = false;
    unconfigured1.render();
    let stamped = unconfigured1.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');
    stamped = unconfigured1.shadowRoot.querySelectorAll('*:not(template):not(dom-if)');
    stamped.forEach(function(n) {
      assert.equal(getComputedStyle(n).display, 'none', 'node was not hidden but should have been');
    });
    assert.equal(unconfigured1.domUpdateHandlerCount, 1);
  });

  test('if=true, restamp=true, everything rendered and visible', function() {
    unconfigured1.domUpdateHandlerCount = 0;
    unconfigured1.$['if-1'].restamp = true;
    unconfigured1.shouldStamp = true;
    unconfigured1.$['if-1'].render();
    let stamped = unconfigured1.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');
    stamped = unconfigured1.shadowRoot.querySelectorAll('*:not(template):not(dom-if)');
    stamped.forEach(function(n) {
      assert.equal(getComputedStyle(n).display, 'inline', 'node was hidden but should not have been');
    });
    assert.equal(unconfigured1.domUpdateHandlerCount, 1);
  });

  test('if=false, restamp=true, everything gone', function() {
    unconfigured1.domUpdateHandlerCount = 0;
    unconfigured1.$['if-1'].restamp = true;
    unconfigured1.shouldStamp = false;
    unconfigured1.$['if-1'].render();
    let stamped = unconfigured1.shadowRoot.querySelectorAll('*:not(template):not(dom-if)');
    assert.equal(unconfigured1.domUpdateHandlerCount, 1);
    assert.equal(stamped.length, 0, 'total stamped count incorrect');
    assert.equal(unconfigured1.domUpdateHandlerCount, 1);
  });

  // repeat, just to get everything rendered again...
  test('if=true: everything rendered and visible', function() {
    unconfigured1.domUpdateHandlerCount = 0;
    unconfigured1.shouldStamp = true;
    unconfigured2.shouldStamp = true;
    unconfigured1.render();
    unconfigured2.render();
    let stamped = unconfigured1.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped.length, 3, 'total stamped count incorrect');
    stamped[0].prop = 'outer';
    assert.equal(unconfigured1.domUpdateHandlerCount, 1);
  });

  test('parent scope binding', function() {
    let stamped = unconfigured1.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    assert.equal(stamped[1].prop, 'outer');
    assert.equal(stamped[2].prop, 'outer');
  });

  test('parent upward upward notification', function() {
    let stamped = unconfigured1.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    stamped[2].prop = 'nice';
    assert.equal(stamped[0].prop, 'nice');
    assert.equal(stamped[1].prop, 'nice');
  });

  test('event handlers', function() {
    let stamped = unconfigured1.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    stamped[0].fire('test1');
    assert.equal(unconfigured1.testHandler1Count, 1);
    stamped[1].fire('test2');
    assert.equal(unconfigured1.testHandler2Count, 1);
    stamped[2].fire('test3');
    assert.equal(unconfigured1.testHandler3Count, 1);
  });

});

suite('notification between two dom-ifs', function() {

  test('change to one scope doesn\'t affect other dom-if', function() {
    let stamped1 = unconfigured1.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');
    let stamped2 = unconfigured2.shadowRoot.querySelectorAll('*:not(template):not(dom-if):not(span)');

    unconfigured1.prop = 'foo';
    unconfigured2.prop = 'bar';
    assert.equal(stamped1[0].prop, 'foo');
    assert.equal(stamped1[1].prop, 'foo');
    assert.equal(stamped1[2].prop, 'foo');
    assert.equal(stamped2[0].prop, 'bar');
    assert.equal(stamped2[1].prop, 'bar');
    assert.equal(stamped2[2].prop, 'bar');
  });
});

suite('structured data controlling if', function() {

  test('item changed with no if field', function() {
    let showing;
    showing = structuredContainer.querySelector('.showing');
    assert.notOk(showing);
    structuredDomBind.item = {show: true};
    structuredDomIf.render();
    showing = structuredContainer.querySelector('.showing');
    assert.ok(showing);
    assert.equal(getComputedStyle(showing).display, 'block');
    structuredDomBind.item = {};
    structuredDomIf.render();
    showing = structuredContainer.querySelector('.showing');
    assert.ok(showing);
    assert.equal(getComputedStyle(showing).display, 'none');
    structuredDomBind.item = {show: true};
    structuredDomIf.render();
    showing = structuredContainer.querySelector('.showing');
    assert.ok(showing);
    assert.equal(getComputedStyle(showing).display, 'block');
  });

  test('item changed with no if field (restamp)', function() {
    let showing;
    structuredDomIf.restamp = true;
    structuredDomIf.if = false;
    structuredDomIf.render();
    showing = structuredContainer.querySelector('.showing');
    assert.notOk(showing);
    structuredDomBind.item = {show: true};
    structuredDomIf.render();
    showing = structuredContainer.querySelector('.showing');
    assert.ok(showing);
    structuredDomBind.item = {};
    structuredDomIf.render();
    showing = structuredContainer.querySelector('.showing');
    assert.notOk(showing);
    structuredDomBind.item = {show: true};
    structuredDomIf.render();
    showing = structuredContainer.querySelector('.showing');
    assert.ok(showing);
  });

});

suite('text node handling', function() {

  test('text nodes cleared on if=false', function() {
    let x = document.createElement('x-textcontent');
    document.body.appendChild(x);
    x.$.domIf.render();
    let stamped = x.shadowRoot.childNodes;
    assert.equal(stamped.length, 12);
    assert.equal(stamped[7].textContent.trim(), 'Stuff');
    x.$.domIf.if = false;
    x.$.domIf.render();
    stamped = x.shadowRoot.childNodes;
    assert.equal(stamped.length, 12);
    assert.equal(stamped[7].textContent.trim(), '');
    x.$.domIf.if = true;
    x.$.domIf.render();
    stamped = x.shadowRoot.childNodes;
    assert.equal(stamped.length, 12);
    assert.equal(stamped[7].textContent.trim(), 'Stuff');
    document.body.removeChild(x);
  });

  test('binding to text nodes changed while if=false', function() {
    let x = document.createElement('x-textcontent');
    document.body.appendChild(x);
    x.$.domIf.render();
    let stamped = x.shadowRoot.childNodes;
    assert.equal(stamped.length, 12);
    assert.equal(stamped[7].textContent.trim(), 'Stuff');
    x.$.domIf.if = false;
    x.$.domIf.render();
    x.text = 'Hollaaaaa!';
    stamped = x.shadowRoot.childNodes;
    assert.equal(stamped.length, 12);
    assert.equal(stamped[7].textContent.trim(), '');
    x.$.domIf.if = true;
    x.$.domIf.render();
    stamped = x.shadowRoot.childNodes;
    assert.equal(stamped.length, 12);
    assert.equal(stamped[7].textContent.trim(), 'Hollaaaaa!');
    document.body.removeChild(x);
  });

});

suite('slot handling', function() {

  test('slots added/removed on if=true/false', function() {
    let x = document.createElement('x-slot');
    let one = document.createElement('div');
    one.slot = 'one';
    x.appendChild(one);
    let two = document.createElement('div');
    two.slot = 'two';
    x.appendChild(two);
    let three = document.createElement('div');
    three.slot = 'three';
    x.appendChild(three);
    document.body.appendChild(x);
    x.$.domIf.render();
    let stamped = x.shadowRoot.childNodes;
    assert.equal(stamped.length, 14);
    assert.equal(stamped[4].assignedNodes()[0], one);
    assert.equal(stamped[8].assignedNodes()[0], two);
    assert.equal(stamped[10].assignedNodes()[0], three);
    x.$.domIf.if = false;
    x.$.domIf.render();
    stamped = x.shadowRoot.childNodes;
    assert.equal(stamped.length, 14);
    x.$.domIf.if = true;
    x.$.domIf.render();
    stamped = x.shadowRoot.childNodes;
    assert.equal(stamped.length, 14);
    assert.equal(stamped[4].assignedNodes()[0], one);
    assert.equal(stamped[8].assignedNodes()[0], two);
    assert.equal(stamped[10].assignedNodes()[0], three);
    x.$.domIf.if = false;
    x.$.domIf.render();
    const innerIf = x.shadowRoot.querySelector('#innerIf');
    innerIf.if = false;
    innerIf.render();
    x.$.domIf.if = true;
    x.$.domIf.render();
    stamped = x.shadowRoot.childNodes;
    assert.equal(stamped[4].assignedNodes()[0], one);
    assert.equal(stamped[7].assignedNodes()[0], two);
    assert.equal(stamped[9].assignedNodes()[0], three);
    document.body.removeChild(x);
  });

});

suite('attach/detach tests', function() {

  test('move domif (clients persist)', function(done) {
    let domif = document.querySelector('#simple');
    domif.if = true;
    innerContainer.appendChild(domif);
    setTimeout(function() {
      let clients = innerContainer.querySelectorAll('x-client');
      // Same clients as before since move happened in one turn
      assert.equal(clients[0].uid, 0);
      assert.equal(clients[1].uid, 1);
      assert.equal(clients[2].uid, 2);
      assert.equal(clients[1].previousElementSibling, clients[0]);
      assert.equal(clients[2].previousElementSibling, clients[1]);
      assert.equal(domif.previousElementSibling, clients[2]);
      done();
    });
  });

  test('remove, wait, append domif (clients recreated)', function(done) {
    let domif = document.querySelector('#simple');
    domif.if = true;
    innerContainer.removeChild(domif);
    setTimeout(function() {
      let clients = innerContainer.querySelectorAll('x-client');
      assert.equal(clients.length, 0);
      innerContainer.appendChild(domif);
      setTimeout(function() {
        let clients = outerContainer.querySelectorAll('x-client');
        // New clients since removed for a turn
        assert.equal(clients[0].uid, 3);
        assert.equal(clients[1].uid, 4);
        assert.equal(clients[2].uid, 5);
        assert.equal(clients[1].previousElementSibling, clients[0]);
        assert.equal(clients[2].previousElementSibling, clients[1]);
        assert.equal(domif.previousElementSibling, clients[2]);
        done();
      });
    });
  });

  test('move host with domif (clients persist)', function(done) {
    let host = document.createElement('x-host');
    outerContainer.appendChild(host);
    setTimeout(function() {
      let clients = host.shadowRoot.querySelectorAll('x-client');
      // New clients created in host instance
      assert.equal(clients[0].uid, 6);
      assert.equal(clients[1].uid, 7);
      assert.equal(clients[2].uid, 8);
      assert.equal(clients[1].previousElementSibling, clients[0]);
      assert.equal(clients[2].previousElementSibling, clients[1]);
      assert.equal(host.$.domif.previousElementSibling, clients[2]);
      innerContainer.appendChild(host);
      setTimeout(function() {
        let clients = host.shadowRoot.querySelectorAll('x-client');
        // Clients in removed host persist
        assert.equal(clients[0].uid, 6);
        assert.equal(clients[1].uid, 7);
        assert.equal(clients[2].uid, 8);
        assert.equal(clients[1].previousElementSibling, clients[0]);
        assert.equal(clients[2].previousElementSibling, clients[1]);
        assert.equal(host.$.domif.previousElementSibling, clients[2]);
        done();
      });
    });
  });

  test('remove, wait, append host with domif (clients persist)', function(done) {
    let host = document.createElement('x-host');
    outerContainer.appendChild(host);
    setTimeout(function() {
      let clients = host.shadowRoot.querySelectorAll('x-client');
      // New clients created in host instance
      assert.equal(clients[0].uid, 9);
      assert.equal(clients[1].uid, 10);
      assert.equal(clients[2].uid, 11);
      assert.equal(clients[1].previousElementSibling, clients[0]);
      assert.equal(clients[2].previousElementSibling, clients[1]);
      assert.equal(host.$.domif.previousElementSibling, clients[2]);
      outerContainer.removeChild(host);
      setTimeout(function() {
        // Clients in removed host persist
        assert.equal(clients[0].uid, 9);
        assert.equal(clients[1].uid, 10);
        assert.equal(clients[2].uid, 11);
        assert.equal(clients[1].previousElementSibling, clients[0]);
        assert.equal(clients[2].previousElementSibling, clients[1]);
        assert.equal(host.$.domif.previousElementSibling, clients[2]);
        innerContainer.appendChild(host);
        setTimeout(function() {
          // Clients in removed host persist
          let clients = host.shadowRoot.querySelectorAll('x-client');
          assert.equal(clients[0].uid, 9);
          assert.equal(clients[1].uid, 10);
          assert.equal(clients[2].uid, 11);
          assert.equal(clients[1].previousElementSibling, clients[0]);
          assert.equal(clients[2].previousElementSibling, clients[1]);
          assert.equal(host.$.domif.previousElementSibling, clients[2]);
          done();
        });
      });
    });
  });

  test('remove, append domif', function(done) {
    let domif = document.querySelector('#simple');
    let parent = domif.parentNode;
    domif.if = true;
    parent.removeChild(domif);
    setTimeout(function() {
      let clients = parent.querySelectorAll('x-client');
      assert.equal(clients.length, 0);
      parent.appendChild(domif);
      setTimeout(function() {
        let clients = parent.querySelectorAll('x-client');
        assert.equal(clients[0].uid, 12);
        assert.equal(clients[1].uid, 13);
        assert.equal(clients[2].uid, 14);
        assert.equal(clients[1].previousElementSibling, clients[0]);
        assert.equal(clients[2].previousElementSibling, clients[1]);
        assert.equal(domif.previousElementSibling, clients[2]);
        done();
      });
    });
  });

  test('move into doc fragment', function(done) {
    let el = shouldBeRemoved;
    assert.equal(el.parentNode, removalContainer);
    let frag = document.createDocumentFragment();
    frag.appendChild(toBeRemoved);
    setTimeout(function() {
      assert.equal(el.parentNode, null);
      removalContainer.appendChild(frag);
      setTimeout(function() {
        assert.equal(shouldBeRemoved.parentNode, removalContainer);
        done();
      });
    });
  });

  test('move into shadow root', function(done) {
    if (window.ShadyCSS && window.ShadyCSS.nativeShadow) {
      let el = shouldBeRemoved;
      assert.equal(el.parentNode, removalContainer);
      let div = document.createElement('div');
      document.body.appendChild(div);
      let frag = div.attachShadow({mode: 'open'});
      frag.appendChild(toBeRemoved);
      setTimeout(function() {
        assert.equal(el.parentNode, frag);
        done();
      });
    } else {
      done();
    }
  });

});

suite('timing', function() {

  test('non-upgrade case finds template', function() {
    assert.equal(nonUpgrade.textContent.trim(), 'stamped');
  });

});

[true, false].forEach(function(restamp) {

  suite('ordering, restamp: ' + restamp, function() {

    test('effects in if not run when `if` goes false via property', function() {
      let el = document.createElement('x-guard-prop');
      el.restamp = restamp;
      document.body.appendChild(el);
      el.bool = true;
      flush();
      assert.equal(el.guarded.callCount, 1);
      assert.equal(el.shadowRoot.textContent.trim(), 'true');
      el.bool = false;
      flush();
      assert.equal(el.guarded.callCount, 1);
      assert.equal(el.shadowRoot.textContent.trim(), '');
      document.body.removeChild(el);
    });

    test('effects in if not run when `if` goes false via inline function', function() {
      let el = document.createElement('x-guard-inline');
      el.restamp = restamp;
      document.body.appendChild(el);
      el.bool = true;
      flush();
      assert.equal(el.guarded.callCount, 1);
      assert.equal(el.shadowRoot.textContent.trim(), 'true');
      el.bool = false;
      flush();
      assert.equal(el.guarded.callCount, 1);
      assert.equal(el.shadowRoot.textContent.trim(), '');
      document.body.removeChild(el);
    });

    test('effects in if not run when `if` goes false via computed property', function() {
      let el = document.createElement('x-guard-computed');
      el.restamp = restamp;
      document.body.appendChild(el);
      el.bool = true;
      flush();
      assert.equal(el.guarded.callCount, 1);
      assert.equal(el.shadowRoot.textContent.trim(), 'true');
      el.bool = false;
      flush();
      assert.equal(el.guarded.callCount, 1);
      assert.equal(el.shadowRoot.textContent.trim(), '');
      document.body.removeChild(el);
    });

    test('effects in if not run when `if` goes false via object sub-property', function() {
      let el = document.createElement('x-guard-object');
      el.restamp = restamp;
      document.body.appendChild(el);
      el.obj = {bool: true};
      flush();
      assert.equal(el.guarded.callCount, 1);
      assert.equal(el.shadowRoot.textContent.trim(), 'true');
      el.obj = {bool: false};
      flush();
      assert.equal(el.guarded.callCount, 1);
      assert.equal(el.shadowRoot.textContent.trim(), '');
      document.body.removeChild(el);
    });

    test('effects in if not run when `if` goes false via computed from object sub-property', function() {
      let el = document.createElement('x-guard-object-computed');
      el.restamp = restamp;
      document.body.appendChild(el);
      el.obj = {bool: true};
      flush();
      assert.equal(el.guarded.callCount, 1);
      assert.equal(el.shadowRoot.textContent.trim(), 'true');
      el.obj = {bool: false};
      flush();
      assert.equal(el.guarded.callCount, 1);
      assert.equal(el.shadowRoot.textContent.trim(), '');
      document.body.removeChild(el);
    });

    test('effects in if not run when `if` goes false via setProperties batch', function() {
      let el = document.createElement('x-guard-separate-props');
      el.restamp = restamp;
      document.body.appendChild(el);
      el.setProperties({a: 'ok', b: true});
      flush();
      assert.equal(el.guarded.callCount, 1);
      assert.equal(el.shadowRoot.textContent.trim(), 'ok');
      el.setProperties({a: 'notok', b: false});
      flush();
      assert.equal(el.guarded.callCount, 1);
      assert.equal(el.shadowRoot.textContent.trim(), '');
      document.body.removeChild(el);
    });

    test('host properties in sync when changed while false', function() {
      let el = document.createElement('x-guard-separate-props');
      el.restamp = restamp;
      document.body.appendChild(el);
      el.b = true;
      flush();
      el.a = 'ok';
      el.c = {d: 'ok'};
      assert.equal(el.shadowRoot.textContent.trim(), 'ok');
      let observer = el.shadowRoot.querySelector('#observer');
      assert.equal(observer.propChanged.callCount, 1);
      assert.equal(observer.pathChanged.callCount, 1);
      assert.equal(observer.pathChanged.getCalls()[0].args[0].path, 'obj');
      el.b = false;
      el.a = 'notok';
      el.set('c.d', 'notok');
      flush();
      assert.equal(el.shadowRoot.textContent.trim(), '');
      if (!restamp) {
        const observer = el.shadowRoot.querySelector('#observer');
        assert.equal(observer.propChanged.callCount, 1);
        assert.equal(observer.pathChanged.callCount, 1);
      }
      el.set('c.d', 'changed');
      el.a = 'changed';
      el.b = true;
      flush();
      assert.equal(el.shadowRoot.textContent.trim(), 'changed');
      observer = el.shadowRoot.querySelector('#observer');
      // Note that path bindings in a falsey dom-if will be updated when the
      // dom-if becomes truthy, however path notifications to other elements
      // will be lost; this is a fundamental limitation (#4818) due to the fact
      // that batched path notifications are not supported. Hence the
      // `propChanged` count increments, but the `pathChanged` count does not.
      assert.equal(observer.propChanged.callCount, restamp ? 1 : 2);
      assert.equal(observer.pathChanged.callCount, 1);
      document.body.removeChild(el);
    });

    test('host properties in sync toggling true-false-true synchronously', function() {
      let el = document.createElement('x-guard-separate-props');
      el.restamp = restamp;
      document.body.appendChild(el);
      el.a = 'initial';
      el.b = true;
      flush();
      assert.equal(el.shadowRoot.textContent.trim(), 'initial');
      el.setProperties({b: false, a: 'changed'});
      el.b = true;
      flush();
      assert.equal(el.shadowRoot.textContent.trim(), 'changed');
      document.body.removeChild(el);
    });

    test('host paths in sync when changed while false', function() {
      let el = document.createElement('x-guard-separate-paths');
      el.restamp = restamp;
      document.body.appendChild(el);
      el.obj = {a: 'ok', b: true};
      flush();
      assert.equal(el.shadowRoot.textContent.trim(), 'ok');
      el.set('obj.b', false);
      el.set('obj.a', 'notok');
      flush();
      assert.equal(el.shadowRoot.textContent.trim(), '');
      el.set('obj.a', 'changed');
      el.set('obj.b', true);
      flush();
      assert.equal(el.shadowRoot.textContent.trim(), 'changed');
      document.body.removeChild(el);
    });

  });

});
</script>

</body>
</html>
